/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework.localization;

import axil.api.AxilType;
import axil.api.Formatting;
import axil.api.Persona;
import axil.engine.Repository;
import axil.etc.Identity;
import axil.etc.Immutable;
import axil.etc.NameValue;
import axil.framework.collection.IdentityMap;
import axil.framework.config.Configuration;
import axil.framework.format.FormatBuilder;
import axil.framework.format.MaskFormatting;
import axil.framework.format.SimpleFormatting;

import java.util.*;


/**
 * The Axil implementation of the Axil API Locale. This is a superset of the
 * Java Locale object, providing additional semantics to support advanced
 * localization and formatting capabilities. Unlike Java localization, all of
 * Axil localization is inherently thread safe.
 */
public class Locality implements Identity, Immutable, axil.api.Locale {
	private static final class Value {
		private String type;
		private String spec;
		private Formatting formatting;


		private Value(String type, String spec) {
			this.type = type;
			this.spec = spec;
			this.formatting = null;
		}

		Formatting formatting() {
			if (formatting == null) {
				AxilType t = Repository.instance().type(type);
				if (t instanceof FormatBuilder) {
					formatting = ((FormatBuilder)t).build(spec);
				} else {
					formatting = new MaskFormatting(spec);
				}
			}
			return formatting;
		}
	}


	private static int counter = 0;
	private static final List<Locality> locales = load();
	private static final IdentityMap<Locality> lookup =
	    new IdentityMap<Locality>(locales);

	private final String ident;
	private final String local;		// Name of this locale in local language
	private final String english;	// Name of this locale, in English
	private final Country country;
	private final Language language;
	private final String separator;
	private final String fallback;
	private final Map<String,Value> formatting;
	private final int index;
	private Locale locale;


	private Locality(Configuration cfg) {
		this.ident = cfg.text("locale-code");
		this.local = cfg.text("local-name");
		this.english = cfg.text("english-name");
		this.country = Country.from(cfg.text("country-code"));
		this.language = Language.from(cfg.text("language-code"));
		this.fallback = cfg.text("fallback-locale", null);
		this.locale = new Locale(ident);
		this.separator = cfg.text("list-separator");
		this.formatting = new HashMap<String, Value>();
		for (NameValue nv : cfg.values("formatting-")) {
			formatting.put(nv.key(), new Value(nv.key(), nv.value().toString()));
		}
		this.index = counter;
		counter ++;
	}


	private static List<Locality> load() {
		List<Locality> list = new ArrayList<Locality>();
		for (String name : Configuration.axil().list("supported-locales")) {
			list.add(new Locality(
			             new Configuration("data/locale/" + name + ".cfg")));
		}
		return list;
	}


	/**
	 * Get the unique identity for this object. The string returned is never
	 * empty and contains no punctuation characters beyond a possible '-'. The
	 * value is suitable for use as an identifier.
	 */
	public String identity() {
		return ident;
	}


	public Country country() {
		return country;
	}


	public Language language() {
		return language;
	}

	/**
	 * The name of this locale is the language of this locale.
	 */
	public String local() {
		return local;
	}


	/**
	 * The name of this locale in US English.
	 */
	public String english() {
		return english;
	}


	public Locality fallback() {
		return fallback == null ? null : from(fallback);
	}


	/**
	 * Get the native Java Locale object underlying this Axil locale.
	 *
	 * @return
	 * 	Returns the native Java locale. The object returned is never null. This
	 * 	is provided only as a convenience for the host application.
	 */
	public java.util.Locale locale() {
		return locale;
	}


	public String separator() {
		return separator;
	}


	/**
	 * Get the default formatting specifications associated with this locale
	 * for the given type. The value returned is never null.
	 */
	public Formatting formatting(AxilType type) {
		Value v = formatting.get(type.identity());
		if (v == null) {
			return SimpleFormatting.object;
		}
		return v.formatting();
	}


	public int index() {
		return index;
	}


	public static Locality from(String ident) {
		return lookup.at(ident);
	}


	public static Locality from(Persona persona) {
		return (Locality)persona.locale();
	}


	public static List<Locality> all() {
		return locales;
	}
}
